package com.atguigu.gmall.index.aspect;

import com.alibaba.fastjson.JSON;


import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;

@Aspect
@Component
public class GmallCacheAspect {
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @Autowired
    RedissonClient redissonClient;

    @Autowired
    RBloomFilter bloomFilter;

    @Around("@annotation(GmallCache)")
    public Object around(ProceedingJoinPoint joinPoint)throws Throwable {
        // 获取方法签名,MethodSignature方法更多
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 方法对象
        Method method = signature.getMethod();
        // 获取方法上的特定注解
        GmallCache gmallCache = method.getAnnotation(GmallCache.class);
        // 获取gmallcache注解中的前缀
        String prefix = gmallCache.prefix();
        // 获取目标方法的参数列表(id) 组装缓存的key
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        String key=prefix+args;
        // 通过bloomFilter判断数据是否存在,不存在直接返回，防止缓存穿透
       if( !bloomFilter.contains(key)){
           return null;
       }
        // 查询缓存，缓存命中直接返回
        String json = stringRedisTemplate.opsForValue().get(key);
        if(StringUtils.isNotBlank(json)){
            //signature.getReturnType()得到目标方法的返回值类型
            return JSON.parseObject(json,signature.getReturnType());
        }
        // 为了防止缓存击穿添加分布式锁
        String lock_prefix = gmallCache.lock();
        RLock lock = redissonClient.getLock(lock_prefix + args);
        lock.lock();
        Object result;
        try {
            // 再查缓存
            String json2 = stringRedisTemplate.opsForValue().get(key);
            if(StringUtils.isNotBlank(json2)){
                //signature.getReturnType()得到目标方法的返回值类型
                return JSON.parseObject(json2,signature.getReturnType());
            }
            // 远程调用或者查询数据库，放入缓存
            result = joinPoint.proceed(joinPoint.getArgs());
            // 放入缓存，释放分布式锁
            int timeout = gmallCache.timeout() + new Random().nextInt(gmallCache.random());
            stringRedisTemplate.opsForValue().set(key,JSON.toJSONString(result),timeout, TimeUnit.MINUTES);
            return result;
        } finally {
            lock.unlock();
        }

    }
}
